﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Timers;
using System.Windows.Controls;
using System.Windows.Media;
using BitsOfStuff.MyActions;
using BitsOfStuff.Support;

namespace BitsOfStuff
{
    /// <summary>
    /// This class provide conversion from freeman sequence to char
    /// </summary>
    public class MyCharRecognizer
    {
        private UserProfile _userProfile;

        private readonly Timer _timer;

        private MyAction _timeredAction;

        private readonly MyShapeRecognize _myShapeRecognize;

        private readonly ActionArbitr _arbitr;

        private MyPoint _decisionShapeStartPoint;
        private MyPoint _decisionShapeEndPoint;

        // ReSharper disable UnassignedField.Local
        private MyVector _decisionVector;
        // ReSharper restore UnassignedField.Local

        private double _decisionHeight;
        /*
                private double _decisionWeight;
        */
        private bool _shift;

        private readonly List<int> _sequenceComplete;

        private List<int> _findSequence;

        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public Timer getTimer()
        {
            return _timer;
        }

        private bool _decisionMode;

        /// <summary>
        ///
        /// </summary>
        public bool posibleCircleShape { get; set; }

        private MyUserProfileNodle _curentNodle;
        private MyUserProfileNodle _curentNodleShadow;

        private bool _lastActionMade;

        /// <summary>
        /// Constructor of class.This is constructor in case, when new profile is created
        /// </summary>
        /// <param name="name">Name of user profile</param>
        /// <param name="myShapeRecognize">Parent class , we are holding references for some calls</param>
        public MyCharRecognizer(string name, MyShapeRecognize myShapeRecognize)
        {
            // new user profile created
            _userProfile = new UserProfile(name, this);
            _myShapeRecognize = myShapeRecognize;

            _lastActionMade = true;
            // timer set to 600ms
            _timer = new Timer(600);
            //Add function to handle finished timer
            _timer.Elapsed += myDecisionActionElepsed;
            _curentNodle = _userProfile.getRootNodle();
            _arbitr = new ActionArbitr(myShapeRecognize, this);
            _sequenceComplete = new List<int>();
            posibleCircleShape = false;
            _shift = false;
        }

        /// <summary>
        /// Function that manage actions after timer finish
        /// </summary>
        /// <param name="sender">Generic sender parameter</param>
        /// <param name="e">Generic arts parameter</param>
        private void myDecisionActionElepsed(object sender, ElapsedEventArgs e)
        {
            // set last action to made
            _lastActionMade = true;
            //Set starting nodle in tree to root
            _curentNodle = _userProfile.getRootNodle();
            // Make action that is stored
            Event(_timeredAction);
            // set all other modes to false
            _decisionMode = false;
            _timer.Enabled = false;
        }

        //public void test()
        //{
        //    MyDecisionAction myDecisionAction = new MyDecisionAction(new MyDoNothingAction(), _timer);
        //    myDecisionAction.makeAction();
        //}

        /// <summary>
        /// Function for loading user profile that is stored. Class user profile is serializable class
        /// </summary>
        /// <param name="filePath">Path to user profile</param>
        public void loadUserProfile(string filePath)
        {
            //created stream
            Stream stream = File.Open(filePath, FileMode.Open);
            //created serialization formatter
            var binaryFormatter = new BinaryFormatter();
            // loaded user profile
            _userProfile = (UserProfile)binaryFormatter.Deserialize(stream);
            //close stream
            stream.Close();
            // set nodle to root
            _curentNodle = _userProfile.getRootNodle();
        }

        /// <summary>
        /// Function for saving user profile . Class user profile is serializable class
        /// </summary>
        /// <param name="filePath">Path to user profile</param>
        public void saveUserProfile(string filePath)
        {
            //create new file
            Stream stream = File.Open(filePath, FileMode.Create);
            // binary formatet
            var binaryFormatter = new BinaryFormatter();

            // writing and closing
            binaryFormatter.Serialize(stream, _userProfile);
            stream.Close();
        }

        /// <summary>
        /// When new profile is loaded, we need to overwrite some local clasees
        /// </summary>
        /// <param name="inkCanvas">References to paper like grafick element</param>
        public void assignNewResorces(InkCanvas inkCanvas)
        {
            _userProfile.assignInkCanvas(inkCanvas);
            _userProfile.setNewCharRecognizer(this);
        }

        /// <summary>
        /// Function that provide storing new chars
        /// </summary>
        /// <param name="sequence">Vector sequence of new char</param>
        /// <param name="action">Action , in whitch is stored new char</param>
        public void addSequenceToProfile(List<int> sequence, MyAction action)
        {
            _userProfile.addSequenc(sequence, action);
            // mark last action to make , we stored new char!
            _lastActionMade = true;
        }

        /// <summary>
        /// "Key" function, provide finding and making chars
        /// </summary>
        /// <param name="findSequence">Vector sequence from myShapeRecognizer Class</param>
        public void findAction(List<int> findSequence)
        {
            // determine, if shape is circle
            if (posibleCircleShape)
            {
                // if yes , make O
                List<MyAction> resultO;
                if (_arbitr.rawAnalysis(posibleCircleShape, out resultO))
                {
                    Event(resultO[0]);
                }
            }

            _sequenceComplete.AddRange(new List<int>(findSequence));
            _footstepAlternativeFirstRun = true;

            // First we find foostep actions
            List<MyAction> posibleFootstepAction = getFootstepAction();
            Debug.WriteLine(Support.DebugFunction.actionsToString("Footsteps", posibleFootstepAction));

            // Second, we find vector action
            List<MyAction> posibleVectorAction = findSequenceVectors(findSequence);
            Debug.WriteLine(Support.DebugFunction.actionsToString("Vectors", posibleVectorAction));

            //We make union of that results
            var result = SupportFunctions.intersectActions(posibleFootstepAction, posibleVectorAction);
            Debug.WriteLine(Support.DebugFunction.actionsToString("Intersect", result));

            // if result is only one, set result as final and make event of him
            if (result != null && result.Count == 1)
            {
                Event(result[0]);
                return;
            }

            // if result is par , we try make decision between them
            if (result != null && result.Count == 2)
            {
                MyDecisionAction decision;
                if (_userProfile.tryIfitsOnDecisionState(result[0], result[1], out decision))
                {
                    Event(decision);
                    return;
                }
                var choosenResult = _arbitr.choose(result);
                if (choosenResult is MyShowAction)
                    Event(choosenResult);
                return;
            }

            // Try find , if one of result is autoritative
            if (posibleFootstepAction != null && posibleFootstepAction.Count == 1 && posibleFootstepAction[0].isFinal())
            {
                Event(posibleFootstepAction[0]);
                return;
            }

            if (posibleVectorAction != null && posibleVectorAction.Count == 1 && posibleVectorAction[0].isFinal())
            {
                Event(posibleVectorAction[0]);
                return;
            }

            // Starting of speculating analysis
            if (posibleFootstepAction != null && posibleVectorAction != null)
            {
                var actionsForSpeculation = posibleFootstepAction.Union(posibleVectorAction);
                var autoritativeActions = actionsForSpeculation.Where(myAction => myAction.isFinal()).ToList();
                var nonAutoritiveActions = actionsForSpeculation.Where(myAction => !myAction.isFinal()).ToList();

                Event(speculatingAnalysis(_findSequence, autoritativeActions));
                if (!_lastActionMade)
                    Event(speculatingAnalysis(_findSequence, nonAutoritiveActions));
            }

            // thrown action , that do nothing, so we w8 for another stroke
            Event(new MyDoNothingAction());
        }

        ///State if footstep result if first, or alternative
        private bool _footstepAlternativeFirstRun = true;

        /// <summary>
        /// Function for searching action baset on footprints
        /// </summary>
        /// <returns>Footstep actions</returns>
        private List<MyAction> getFootstepAction()
        {
            var posibleFootstepAction = new List<MyAction>();

            // if we are asking for footsteps firs time
            if (_footstepAlternativeFirstRun)
            {
                posibleFootstepAction = _userProfile.getFootStepAction(FootstepRecognition.getFootStepString());
                _footstepAlternativeFirstRun = false;
            }

            // if result is empty
            if (posibleFootstepAction == null)
            {
                // calculate alternative footsteps
                posibleFootstepAction = _userProfile.getFootStepAction(FootstepRecognition.getAlternativeFootStepString());
                while (posibleFootstepAction == null && FootstepRecognition.haveNoAlternativeFootSteps())
                {
                    // until all actions are found
                    posibleFootstepAction = _userProfile.getFootStepAction(FootstepRecognition.getAlternativeFootStepString());
                }
            }
            return posibleFootstepAction;
        }

        /// <summary>
        /// function for finding vector actions
        /// </summary>
        /// <param name="findSequence">Vector sequence to find</param>
        /// <returns>Vector actions</returns>
        public List<MyAction> findSequenceVectors(List<int> findSequence)
        {
            return this.findSequenceVector(findSequence, _curentNodle);
        }

        /// <summary>
        /// Function for finding vector sequences , but when axxes is little noved
        /// </summary>
        /// <param name="findSequence">Vector sequence to find</param>
        public void findSequenceVectorShadow(List<int> findSequence)
        {
            this.findSequenceVector(findSequence, _curentNodleShadow);
        }

        /// <summary>
        /// Function for finging vector sequences
        /// </summary>
        /// <param name="findSequence">Vector sequence for finding</param>
        /// <param name="searchStartNodle">Starting point in tree</param>
        /// <returns>Listf of actions that belongs to sequenc</returns>
        private List<MyAction> findSequenceVector(List<int> findSequence, MyUserProfileNodle searchStartNodle)
        {
            if (_lastActionMade)
            {
                // if last action is made, than we start from root
                _curentNodle = _userProfile.getRootNodle();
                _curentNodleShadow = (MyUserProfileNodle)_userProfile.getRootNodle().Clone();
            }
            // if we contione from some noodle
            _findSequence = new List<int>(findSequence);
            return _userProfile.findSequence(findSequence, ref _curentNodle);
        }

        /// <summary>
        /// Function for finding footsteps in user profile
        /// </summary>
        /// <param name="footStep">Footstep sequenc</param>
        /// <returns>Footstep actions</returns>
        public List<MyAction> findSequenceFootStep(string footStep)
        {
            return _userProfile.getFootStepAction(footStep);
        }

        /// <summary>
        /// Imput point of Event of MyCharRecognizer
        /// </summary>
        /// <param name="action">Action that are manage</param>
        public void Event(MyAction action)
        {
            if (action is MyDoNothingAction)
            {
                Event((MyDoNothingAction)action);
            }
            if (action is MyShowAction)
            {
                Event((MyShowAction)action);
                return;
            }
            if (action is MyDecisionAction)
            {
                Event((MyDecisionAction)action);
                return;
            }
            if (action is MyCharNotFoundAction)
            {
                Event((MyCharNotFoundAction)action);
                return;
            }
        }

        /// <summary>
        /// Event thats manage actiong when now char is found
        /// </summary>
        private void Event(MyCharNotFoundAction action)
        {
            _lastActionMade = false;
            _footstepAlternativeFirstRun = true;
            //Support.DebugFunction.showDebug(_findSequence);
            var tempAction = action as MyAction;
            _footstepAlternativeFirstRun = true;
            _myShapeRecognize.Event(action);
        }

        /// <summary>
        /// Event thats manage action that holds char , it manage show char, write char and clean all states
        /// </summary>
        /// <param name="action">Action whitch holds char</param>
        private void Event(MyShowAction action)
        {
            Support.DebugFunction.addCharToDebug(action.getChar());

            showTextBox(action.getChar());
            SupportFunctions.sendKey(action.getChar(), ref _shift);
            _lastActionMade = true;
            _sequenceComplete.Clear();
            _myShapeRecognize.setUndefinitive();
        }

        /// <summary>
        /// Event thats manage actiong whitch are decising between two actions
        /// </summary>
        /// <param name="action">Starting decision action</param>
        private void Event(MyDecisionAction action)
        {
            SupportDrawnFunctions.switchTimerSig();
            _timer.Enabled = true;
            _decisionMode = true;
            _timeredAction = action.getTimeredAction();
            setDecisionState();
        }

        /// <summary>
        /// Event thats manage actiong whitch do nothing
        /// </summary>
        /// <param name="action">Impout DoNothingAction</param>
        private void Event(MyDoNothingAction action)
        {
            _lastActionMade = false;
            _footstepAlternativeFirstRun = true;
        }

        /// <summary>
        /// Stops all curent working actions
        /// </summary>
        public void deleteAllCurentAction()
        {
            stopTimer();
            _lastActionMade = true;
            _sequenceComplete.Clear();
        }

        /// <summary>
        /// Function that stops timer
        /// </summary>
        public void stopTimer()
        {
            if (_timer.Enabled)
            {
                SupportDrawnFunctions.switchTimerSig();
            }
            _timer.Enabled = false;
        }

        /// <summary>
        /// Function for safe thread showing found char on user screan
        /// </summary>
        /// <param name="c">char that are showed</param>
        private void showTextBox(char c)
        {
            if (_myShapeRecognize.getInkCanvas().Dispatcher.CheckAccess())
            {
                showTextBoxSafe(c);
            }
            else
            {
                _myShapeRecognize.getInkCanvas().Dispatcher.Invoke(new Action(() => showTextBoxSafe(c)));
            }
        }

        /// <summary>
        /// Function that make text box to show in user imput in app astyle
        /// </summary>
        /// <returns>Strict formated RichTextBox</returns>
        [STAThread]
        private RichTextBox makeTextBox()
        {
            RichTextBox myTextBox = new RichTextBox();

            myTextBox.Height = 200;
            myTextBox.Width = 200;
            myTextBox.FontSize = 150;
            // Font used in wp8 :)
            myTextBox.FontFamily = new FontFamily("Segoe");
            Color color = new Color();
            color.A = 153;
            color.R = 59;
            color.G = 55;
            color.B = 55;
            myTextBox.Foreground = new SolidColorBrush(color);
            Brush backgraund = new DrawingBrush();
            backgraund.Opacity = 0;
            myTextBox.BorderBrush = backgraund;
            myTextBox.Background = backgraund;

            return myTextBox;
        }

        /// <summary>
        /// Function for thread safe showing char on user screan
        /// </summary>
        /// <param name="c">Char to show</param>
        [STAThread]
        private void showTextBoxSafe(char c)
        {
            if (!_shift && ((byte)c > 64))
            {
                c = (char)(((byte)c) + 32);
            }

            var myTextBox = makeTextBox();
            myTextBox.AppendText(c.ToString(CultureInfo.InvariantCulture));
            SupportDrawnFunctions.reDrawnInkCanvas(_myShapeRecognize.getInkCanvas());
            //Can acees on curent thread

            //MyPoint firsPoint = _myShapeRecognize.getLastStrokeFirstPoint();
            InkCanvas.SetLeft(myTextBox, 300);
            InkCanvas.SetTop(myTextBox, 50);
            _myShapeRecognize.getInkCanvas().Children.Add(myTextBox);
        }

        /// <summary>
        /// Function for give action , that are made last
        /// </summary>
        /// <returns>Last made function</returns>
        public bool getLastActionMade()
        {
            return _lastActionMade;
        }

        /// <summary>
        /// Function inform about decision state
        /// </summary>
        /// <returns>Return true if its in decision state, return falce otherwise</returns>
        public bool isInDecisionState()
        {
            return _decisionMode;
        }

        /// <summary>
        /// This function start decision stete, its start timer and make border in whitch user start typing , decision mode end
        /// </summary>
        public void setDecisionState()
        {
            _myShapeRecognize.getLastStrokeStarPoints(ref  _decisionShapeStartPoint,
                                                        ref _decisionShapeEndPoint);
            _decisionHeight = _decisionShapeStartPoint.getDistanceToPoint(_decisionShapeEndPoint) / (2.5);

            _decisionVector.makeVector(_decisionShapeStartPoint, _decisionShapeEndPoint);
        }

        /// <summary>
        /// Function for seting all states to right walues
        /// </summary>
        private void endDecisionState()
        {
            _timer.Enabled = false;
            _timeredAction = null;
            _decisionMode = false;
            _lastActionMade = false;
            _footstepAlternativeFirstRun = true;
        }

        /// <summary>
        /// Function for ending decision state
        /// </summary>
        /// <param name="myPoint">First stylus point in decision mode</param>
        public void tryEndDecisionState(MyPoint myPoint)
        {
            if (isNearDecisionVector(myPoint))
            {
                endDecisionState();
            }
            else
            {
                Event(_timeredAction);
            }
        }

        /// <summary>
        /// Function for stop decision mode
        /// </summary>
        /// <param name="myPoint"> Point where first stylus point in decision mode are pointing</param>
        /// <returns>Return if this point is in tollerance of distance</returns>
        public bool isNearDecisionVector(MyPoint myPoint)
        {
            return _decisionVector.distance(myPoint) < _decisionHeight;
        }

        /// <summary>
        /// Function for geting last action, that are made
        /// </summary>
        /// <returns>Last made action</returns>
        public bool lastActionMade()
        {
            return _lastActionMade;
        }

        /// <summary>
        /// Class for speculation analysing whitch result will be most similary char
        /// </summary>
        /// <param name="sequence">Sequence of char , that is example </param>
        /// <param name="posibleAction">List of all action that are canditate for resutl char</param>
        /// <returns>Returns most simmilar char</returns>
        private MyAction speculatingAnalysis(List<int> sequence, IEnumerable<MyAction> posibleAction)
        {
            //Imput condition if posible chars are not empty
            if (posibleAction == null)
                return new MyCharNotFoundAction();

            //Imput condition if posible char are only one , if yes return him.
            // ReSharper disable PossibleMultipleEnumeration
            int count = posibleAction.Count();
            // ReSharper restore PossibleMultipleEnumeration
            if (count == 1)
                // ReSharper disable PossibleMultipleEnumeration
                return posibleAction.FirstOrDefault();
            // ReSharper restore PossibleMultipleEnumeration

            //convert vector sequenc to string for better manipulations
            string sequenceString = SupportFunctions.convertFromSequenceToString(sequence);

            //Cast generic MyAction to more specific MyShowAction , whitch holds char
            // ReSharper disable PossibleMultipleEnumeration
            var posibleFootstpesShow = posibleAction.OfType<MyShowAction>().ToList();
            // ReSharper restore PossibleMultipleEnumeration

            //Get all chars from actions , so in list will be all posible chars
            List<char> posibleChars = posibleFootstpesShow.Select(myShowAction => myShowAction.getChar()).ToList();

            // In this list we stored all posible vector sequence in whitch way the char can be write
            List<List<string>> posibleCharStrings = posibleChars.Select(posibleChar => _userProfile.getFootStepSequence(posibleChar)).ToList();

            //Calculate lewenstain distance from imput string from all posible way of writing
            List<List<int>> posibleCharDistances = posibleCharStrings.Select(posibleCharString => posibleCharString.Select(s => SupportFunctions.calculateDistance(sequenceString, s)).ToList()).ToList();

            //Find char whit minumum distance
            List<int> posibleCharDistancesMinimum = posibleCharDistances.Select(i => i.Min()).ToList();

            for (int i = 0; i < posibleChars.Count; i++)
            {
                String debugLine = String.Format("{0} : {1}", posibleChars[i], posibleCharDistancesMinimum[i]);
                Debug.WriteLine(debugLine);
            }

            if (posibleCharDistancesMinimum.Count == 0)
                return new MyCharNotFoundAction();

            //if (posibleCharDistancesMinimum.Count == 1)
            if (true)
            {
                int minIndex = posibleCharDistancesMinimum.IndexOf(posibleCharDistancesMinimum.Min());

                if (posibleCharDistancesMinimum[minIndex] < MyParameters._charMaximumTolerantDistance)

                //Posible char found , now determinite , if contains more than one stroke
                {
                    //string closest = posibleCharStrings[minIndex][posibleCharDistancesMinimumIndexes[minIndex]];
                    //for (int i = 0; i < closest.Length - 1; i++)
                    //{
                    //    if (closest[i] == '|')
                    //        return new MyDoNothingAction();
                    //}
                    return posibleFootstpesShow[minIndex];
                }

                return new MyDoNothingAction();
            }
        }

        /// <summary>
        /// Move throo function , move request to remove stored action from user profile
        /// </summary>
        /// <param name="action">Action , whitch char will be removed</param>
        public void removeAction(MyShowAction action)
        {
            _userProfile.removeStoredChar(action);
        }
    }
}